#pragma once
#include "Array.h"

enum { MAX_BONE_NUM = 256 };

//
// Bone
//
struct Bone
{
	WORD Index;
	size_t ParentIndex;

	Vector3 InitTranslate;
	Quaternion InitOrientation;
	Vector3 InitScale;

	Vector3 mBindDerivedInversePosition;
	Quaternion mBindDerivedInverseOrientation;
	Vector3 mBindDerivedInverseScale;
};
typedef Array<Bone> BoneArray;

//
// BoneCollection
//
struct BoneCollection
{
	BoneArray Bones;
	NameIndexMap BoneNameIndexMap;
};

//
// BoneInstance
//
struct BoneInstance
{
	const struct Bone* BaseBone;
	bool NeedUpdate;

	Vector3 Translate;
	Quaternion Orientation;
	Vector3 Scale;

	Vector3 DerivedPosition;
	Quaternion DerivedOrientation;
	Vector3 DerivedScale;
};

//
// BoneInstanceArray
//
class BoneInstanceArray : public Array<BoneInstance>
{
public:
	BoneInstanceArray(const BoneCollection& bones)
	{
		Resize(bones.Bones.Size());

		for (size_t i = 0; i < bones.Bones.Size(); i++)
			Get(i).BaseBone = &bones.Bones.Get(i);
	}

	void UpdateTransform(bool forceUpdate = false)
	{
		for (size_t i = 0; i < Size(); i++)
		{
			BoneInstance& bone = Get(i);
			const Bone* baseBone = bone.BaseBone;
			if (!forceUpdate && !bone.NeedUpdate)
				continue;

			// derive from parent.
			if (baseBone->ParentIndex < Size() && baseBone->ParentIndex != baseBone->Index)
			{
				BoneInstance& parent = Get(baseBone->ParentIndex);
				bone.DerivedOrientation = parent.DerivedOrientation * bone.Orientation;
				bone.DerivedScale = parent.DerivedScale * bone.Scale;
				bone.DerivedPosition = parent.DerivedPosition +
					parent.DerivedOrientation * (parent.DerivedScale * bone.Translate);

			}
			else
			{
				bone.DerivedOrientation = bone.Orientation;
				bone.DerivedScale = bone.Scale;
				bone.DerivedPosition = bone.Translate;
			}

			bone.NeedUpdate = false;
		}
	}

	void GetBoneMatrices(Matrix* pMatrices)
	{
		for (size_t i = 0; i < Size(); i++)
		{
			BoneInstance& bone = Get(i);
			Vector3 scale = bone.DerivedScale * bone.BaseBone->mBindDerivedInverseScale;
			Quaternion rotate = bone.DerivedOrientation * bone.BaseBone->mBindDerivedInverseOrientation;
			Vector3 translate = bone.DerivedPosition +
				rotate * (scale * bone.BaseBone->mBindDerivedInversePosition);

			*pMatrices++ = Matrix::ScaleRotationTranslationMatrix(
				scale,
				rotate,
				translate);
		}
	}

	void ResetBones()
	{
		// reset all of the bones to the init position.
		for (size_t i = 0; i < Size(); i++)
		{
			BoneInstance& bone = Get(i);
			bone.Translate = bone.BaseBone->InitTranslate;
			bone.Orientation = bone.BaseBone->InitOrientation;
			bone.Scale = bone.BaseBone->InitScale;
			bone.NeedUpdate = false;
		}
	}
};
